using ICSharpCode.SharpZipLib.Checksums;
using System;
using System.IO;
namespace ICSharpCode.SharpZipLib.BZip2
{
	public class BZip2InputStream : Stream
	{
		private const int START_BLOCK_STATE = 1;
		private const int RAND_PART_A_STATE = 2;
		private const int RAND_PART_B_STATE = 3;
		private const int RAND_PART_C_STATE = 4;
		private const int NO_RAND_PART_A_STATE = 5;
		private const int NO_RAND_PART_B_STATE = 6;
		private const int NO_RAND_PART_C_STATE = 7;
		private int last;
		private int origPtr;
		private int blockSize100k;
		private bool blockRandomised;
		private int bsBuff;
		private int bsLive;
		private IChecksum mCrc = new StrangeCRC();
		private bool[] inUse = new bool[256];
		private int nInUse;
		private byte[] seqToUnseq = new byte[256];
		private byte[] unseqToSeq = new byte[256];
		private byte[] selector = new byte[18002];
		private byte[] selectorMtf = new byte[18002];
		private int[] int_0;
		private byte[] byte_0;
		private int[] unzftab = new int[256];
		private int[][] limit = new int[6][];
		private int[][] baseArray = new int[6][];
		private int[][] perm = new int[6][];
		private int[] minLens = new int[6];
		private Stream baseStream;
		private bool streamEnd;
		private int currentChar = -1;
		private int currentState = 1;
		private int storedBlockCRC;
		private int storedCombinedCRC;
		private int computedBlockCRC;
		private uint computedCombinedCRC;
		private int count;
		private int chPrev;
		private int int_1;
		private int tPos;
		private int rNToGo;
		private int rTPos;
		private int int_2;
		private int int_3;
		private byte byte_1;
		private bool isStreamOwner = true;
		public bool IsStreamOwner
		{
			get
			{
				return this.isStreamOwner;
			}
			set
			{
				this.isStreamOwner = value;
			}
		}
		public override bool CanRead
		{
			get
			{
				return this.baseStream.get_CanRead();
			}
		}
		public override bool CanSeek
		{
			get
			{
				return this.baseStream.get_CanSeek();
			}
		}
		public override bool CanWrite
		{
			get
			{
				return false;
			}
		}
		public override long Length
		{
			get
			{
				return this.baseStream.get_Length();
			}
		}
		public override long Position
		{
			get
			{
				return this.baseStream.get_Position();
			}
			set
			{
				throw new NotSupportedException("BZip2InputStream position cannot be set");
			}
		}
		public BZip2InputStream(Stream stream)
		{
			for (int i = 0; i < 6; i++)
			{
				this.limit[i] = new int[258];
				this.baseArray[i] = new int[258];
				this.perm[i] = new int[258];
			}
			this.BsSetStream(stream);
			this.Initialize();
			this.InitBlock();
			this.SetupBlock();
		}
		public override void Flush()
		{
			if (this.baseStream != null)
			{
				this.baseStream.Flush();
			}
		}
		public override long Seek(long offset, SeekOrigin origin)
		{
			throw new NotSupportedException("BZip2InputStream Seek not supported");
		}
		public override void SetLength(long value)
		{
			throw new NotSupportedException("BZip2InputStream SetLength not supported");
		}
		public override void Write(byte[] buffer, int offset, int count)
		{
			throw new NotSupportedException("BZip2InputStream Write not supported");
		}
		public override void WriteByte(byte value)
		{
			throw new NotSupportedException("BZip2InputStream WriteByte not supported");
		}
		public override int Read(byte[] buffer, int offset, int count)
		{
			if (buffer == null)
			{
				throw new ArgumentNullException("buffer");
			}
			int result;
			for (int i = 0; i < count; i++)
			{
				int num = this.ReadByte();
				if (num == -1)
				{
					result = i;
					return result;
				}
				buffer[offset + i] = (byte)num;
			}
			result = count;
			return result;
		}
		public override void Close()
		{
			if (this.IsStreamOwner && this.baseStream != null)
			{
				this.baseStream.Close();
			}
		}
		public override int ReadByte()
		{
			int result;
			if (this.streamEnd)
			{
				result = -1;
			}
			else
			{
				int num = this.currentChar;
				switch (this.currentState)
				{
				case 3:
					this.SetupRandPartB();
					break;
				case 4:
					this.SetupRandPartC();
					break;
				case 6:
					this.SetupNoRandPartB();
					break;
				case 7:
					this.SetupNoRandPartC();
					break;
				}
				result = num;
			}
			return result;
		}
		private void MakeMaps()
		{
			this.nInUse = 0;
			for (int i = 0; i < 256; i++)
			{
				if (this.inUse[i])
				{
					this.seqToUnseq[this.nInUse] = (byte)i;
					this.unseqToSeq[i] = (byte)this.nInUse;
					this.nInUse++;
				}
			}
		}
		private void Initialize()
		{
			char c = this.BsGetUChar();
			char c2 = this.BsGetUChar();
			char c3 = this.BsGetUChar();
			char c4 = this.BsGetUChar();
			if (c != 'B' || c2 != 'Z' || c3 != 'h' || c4 < '1' || c4 > '9')
			{
				this.streamEnd = true;
			}
			else
			{
				this.SetDecompressStructureSizes((int)(c4 - '0'));
				this.computedCombinedCRC = 0u;
			}
		}
		private void InitBlock()
		{
			char c = this.BsGetUChar();
			char c2 = this.BsGetUChar();
			char c3 = this.BsGetUChar();
			char c4 = this.BsGetUChar();
			char c5 = this.BsGetUChar();
			char c6 = this.BsGetUChar();
			if (c == '\u0017' && c2 == 'r' && c3 == 'E' && c4 == '8' && c5 == 'P' && c6 == '\u0090')
			{
				this.Complete();
			}
			else
			{
				if (c != '1' || c2 != 'A' || c3 != 'Y' || c4 != '&' || c5 != 'S' || c6 != 'Y')
				{
					BZip2InputStream.BadBlockHeader();
					this.streamEnd = true;
				}
				else
				{
					this.storedBlockCRC = this.BsGetInt32();
					this.blockRandomised = (this.BsR(1) == 1);
					this.GetAndMoveToFrontDecode();
					this.mCrc.Reset();
					this.currentState = 1;
				}
			}
		}
		private void EndBlock()
		{
			this.computedBlockCRC = (int)this.mCrc.Value;
			if (this.storedBlockCRC != this.computedBlockCRC)
			{
				BZip2InputStream.CrcError();
			}
			this.computedCombinedCRC = ((this.computedCombinedCRC << 1 & 4294967295u) | this.computedCombinedCRC >> 31);
			this.computedCombinedCRC ^= (uint)this.computedBlockCRC;
		}
		private void Complete()
		{
			this.storedCombinedCRC = this.BsGetInt32();
			if (this.storedCombinedCRC != (int)this.computedCombinedCRC)
			{
				BZip2InputStream.CrcError();
			}
			this.streamEnd = true;
		}
		private void BsSetStream(Stream stream)
		{
			this.baseStream = stream;
			this.bsLive = 0;
			this.bsBuff = 0;
		}
		private void FillBuffer()
		{
			int num = 0;
			try
			{
				num = this.baseStream.ReadByte();
			}
			catch (Exception)
			{
				BZip2InputStream.CompressedStreamEOF();
			}
			if (num == -1)
			{
				BZip2InputStream.CompressedStreamEOF();
			}
			this.bsBuff = (this.bsBuff << 8 | (num & 255));
			this.bsLive += 8;
		}
		private int BsR(int int_4)
		{
			while (this.bsLive < int_4)
			{
				this.FillBuffer();
			}
			int result = this.bsBuff >> this.bsLive - int_4 & (1 << int_4) - 1;
			this.bsLive -= int_4;
			return result;
		}
		private char BsGetUChar()
		{
			return (char)this.BsR(8);
		}
		private int BsGetIntVS(int numBits)
		{
			return this.BsR(numBits);
		}
		private int BsGetInt32()
		{
			int num = this.BsR(8);
			num = (num << 8 | this.BsR(8));
			num = (num << 8 | this.BsR(8));
			return num << 8 | this.BsR(8);
		}
		private void RecvDecodingTables()
		{
			char[][] array = new char[6][];
			for (int i = 0; i < 6; i++)
			{
				array[i] = new char[258];
			}
			bool[] array2 = new bool[16];
			for (int i = 0; i < 16; i++)
			{
				array2[i] = (this.BsR(1) == 1);
			}
			for (int i = 0; i < 16; i++)
			{
				if (array2[i])
				{
					for (int j = 0; j < 16; j++)
					{
						this.inUse[i * 16 + j] = (this.BsR(1) == 1);
					}
				}
				else
				{
					for (int j = 0; j < 16; j++)
					{
						this.inUse[i * 16 + j] = false;
					}
				}
			}
			this.MakeMaps();
			int num = this.nInUse + 2;
			int num2 = this.BsR(3);
			int num3 = this.BsR(15);
			for (int i = 0; i < num3; i++)
			{
				int j = 0;
				while (this.BsR(1) == 1)
				{
					j++;
				}
				this.selectorMtf[i] = (byte)j;
			}
			byte[] array3 = new byte[6];
			for (int k = 0; k < num2; k++)
			{
				array3[k] = (byte)k;
			}
			for (int i = 0; i < num3; i++)
			{
				int k = (int)this.selectorMtf[i];
				byte b = array3[k];
				while (k > 0)
				{
					array3[k] = array3[k - 1];
					k--;
				}
				array3[0] = b;
				this.selector[i] = b;
			}
			for (int l = 0; l < num2; l++)
			{
				int num4 = this.BsR(5);
				for (int i = 0; i < num; i++)
				{
					while (this.BsR(1) == 1)
					{
						if (this.BsR(1) == 0)
						{
							num4++;
						}
						else
						{
							num4--;
						}
					}
					array[l][i] = (char)num4;
				}
			}
			for (int l = 0; l < num2; l++)
			{
				int num5 = 32;
				int num6 = 0;
				for (int i = 0; i < num; i++)
				{
					num6 = Math.Max(num6, (int)array[l][i]);
					num5 = Math.Min(num5, (int)array[l][i]);
				}
				BZip2InputStream.HbCreateDecodeTables(this.limit[l], this.baseArray[l], this.perm[l], array[l], num5, num6, num);
				this.minLens[l] = num5;
			}
		}
		private void GetAndMoveToFrontDecode()
		{
			byte[] array = new byte[256];
			int num = 100000 * this.blockSize100k;
			this.origPtr = this.BsGetIntVS(24);
			this.RecvDecodingTables();
			int num2 = this.nInUse + 1;
			int num3 = -1;
			int num4 = 0;
			for (int i = 0; i <= 255; i++)
			{
				this.unzftab[i] = 0;
			}
			for (int i = 0; i <= 255; i++)
			{
				array[i] = (byte)i;
			}
			this.last = -1;
			if (num4 == 0)
			{
				num3++;
				num4 = 50;
			}
			num4--;
			int num5 = (int)this.selector[num3];
			int num6 = this.minLens[num5];
			int j;
			int num7;
			for (j = this.BsR(num6); j > this.limit[num5][num6]; j = (j << 1 | num7))
			{
				if (num6 > 20)
				{
					throw new BZip2Exception("Bzip data error");
				}
				num6++;
				while (this.bsLive < 1)
				{
					this.FillBuffer();
				}
				num7 = (this.bsBuff >> this.bsLive - 1 & 1);
				this.bsLive--;
			}
			if (j - this.baseArray[num5][num6] < 0 || j - this.baseArray[num5][num6] >= 258)
			{
				throw new BZip2Exception("Bzip data error");
			}
			int num8 = this.perm[num5][j - this.baseArray[num5][num6]];
			while (num8 != num2)
			{
				if (num8 == 0 || num8 == 1)
				{
					int k = -1;
					int num9 = 1;
					do
					{
						if (num8 == 0)
						{
							k += num9;
						}
						else
						{
							if (num8 == 1)
							{
								k += 2 * num9;
							}
						}
						num9 <<= 1;
						if (num4 == 0)
						{
							num3++;
							num4 = 50;
						}
						num4--;
						num5 = (int)this.selector[num3];
						num6 = this.minLens[num5];
						for (j = this.BsR(num6); j > this.limit[num5][num6]; j = (j << 1 | num7))
						{
							num6++;
							while (this.bsLive < 1)
							{
								this.FillBuffer();
							}
							num7 = (this.bsBuff >> this.bsLive - 1 & 1);
							this.bsLive--;
						}
						num8 = this.perm[num5][j - this.baseArray[num5][num6]];
					}
					while (num8 == 0 || num8 == 1);
					k++;
					byte b = this.seqToUnseq[(int)array[0]];
					this.unzftab[(int)b] += k;
					while (k > 0)
					{
						this.last++;
						this.byte_0[this.last] = b;
						k--;
					}
					if (this.last >= num)
					{
						BZip2InputStream.BlockOverrun();
					}
				}
				else
				{
					this.last++;
					if (this.last >= num)
					{
						BZip2InputStream.BlockOverrun();
					}
					byte b2 = array[num8 - 1];
					this.unzftab[(int)this.seqToUnseq[(int)b2]]++;
					this.byte_0[this.last] = this.seqToUnseq[(int)b2];
					for (int l = num8 - 1; l > 0; l--)
					{
						array[l] = array[l - 1];
					}
					array[0] = b2;
					if (num4 == 0)
					{
						num3++;
						num4 = 50;
					}
					num4--;
					num5 = (int)this.selector[num3];
					num6 = this.minLens[num5];
					for (j = this.BsR(num6); j > this.limit[num5][num6]; j = (j << 1 | num7))
					{
						num6++;
						while (this.bsLive < 1)
						{
							this.FillBuffer();
						}
						num7 = (this.bsBuff >> this.bsLive - 1 & 1);
						this.bsLive--;
					}
					num8 = this.perm[num5][j - this.baseArray[num5][num6]];
				}
			}
		}
		private void SetupBlock()
		{
			int[] array = new int[257];
			array[0] = 0;
			Array.Copy(this.unzftab, 0, array, 1, 256);
			for (int i = 1; i <= 256; i++)
			{
				array[i] += array[i - 1];
			}
			for (int i = 0; i <= this.last; i++)
			{
				byte b = this.byte_0[i];
				this.int_0[array[(int)b]] = i;
				array[(int)b]++;
			}
			this.tPos = this.int_0[this.origPtr];
			this.count = 0;
			this.int_2 = 0;
			this.int_1 = 256;
			if (this.blockRandomised)
			{
				this.rNToGo = 0;
				this.rTPos = 0;
				this.SetupRandPartA();
			}
			else
			{
				this.SetupNoRandPartA();
			}
		}
		private void SetupRandPartA()
		{
			if (this.int_2 <= this.last)
			{
				this.chPrev = this.int_1;
				this.int_1 = (int)this.byte_0[this.tPos];
				this.tPos = this.int_0[this.tPos];
				if (this.rNToGo == 0)
				{
					this.rNToGo = BZip2Constants.RandomNumbers[this.rTPos];
					this.rTPos++;
					if (this.rTPos == 512)
					{
						this.rTPos = 0;
					}
				}
				this.rNToGo--;
				this.int_1 ^= ((this.rNToGo == 1) ? 1 : 0);
				this.int_2++;
				this.currentChar = this.int_1;
				this.currentState = 3;
				this.mCrc.Update(this.int_1);
			}
			else
			{
				this.EndBlock();
				this.InitBlock();
				this.SetupBlock();
			}
		}
		private void SetupNoRandPartA()
		{
			if (this.int_2 <= this.last)
			{
				this.chPrev = this.int_1;
				this.int_1 = (int)this.byte_0[this.tPos];
				this.tPos = this.int_0[this.tPos];
				this.int_2++;
				this.currentChar = this.int_1;
				this.currentState = 6;
				this.mCrc.Update(this.int_1);
			}
			else
			{
				this.EndBlock();
				this.InitBlock();
				this.SetupBlock();
			}
		}
		private void SetupRandPartB()
		{
			if (this.int_1 != this.chPrev)
			{
				this.currentState = 2;
				this.count = 1;
				this.SetupRandPartA();
			}
			else
			{
				this.count++;
				if (this.count >= 4)
				{
					this.byte_1 = this.byte_0[this.tPos];
					this.tPos = this.int_0[this.tPos];
					if (this.rNToGo == 0)
					{
						this.rNToGo = BZip2Constants.RandomNumbers[this.rTPos];
						this.rTPos++;
						if (this.rTPos == 512)
						{
							this.rTPos = 0;
						}
					}
					this.rNToGo--;
					this.byte_1 ^= ((this.rNToGo == 1) ? 1 : 0);
					this.int_3 = 0;
					this.currentState = 4;
					this.SetupRandPartC();
				}
				else
				{
					this.currentState = 2;
					this.SetupRandPartA();
				}
			}
		}
		private void SetupRandPartC()
		{
			if (this.int_3 < (int)this.byte_1)
			{
				this.currentChar = this.int_1;
				this.mCrc.Update(this.int_1);
				this.int_3++;
			}
			else
			{
				this.currentState = 2;
				this.int_2++;
				this.count = 0;
				this.SetupRandPartA();
			}
		}
		private void SetupNoRandPartB()
		{
			if (this.int_1 != this.chPrev)
			{
				this.currentState = 5;
				this.count = 1;
				this.SetupNoRandPartA();
			}
			else
			{
				this.count++;
				if (this.count >= 4)
				{
					this.byte_1 = this.byte_0[this.tPos];
					this.tPos = this.int_0[this.tPos];
					this.currentState = 7;
					this.int_3 = 0;
					this.SetupNoRandPartC();
				}
				else
				{
					this.currentState = 5;
					this.SetupNoRandPartA();
				}
			}
		}
		private void SetupNoRandPartC()
		{
			if (this.int_3 < (int)this.byte_1)
			{
				this.currentChar = this.int_1;
				this.mCrc.Update(this.int_1);
				this.int_3++;
			}
			else
			{
				this.currentState = 5;
				this.int_2++;
				this.count = 0;
				this.SetupNoRandPartA();
			}
		}
		private void SetDecompressStructureSizes(int newSize100k)
		{
			if (0 > newSize100k || newSize100k > 9 || 0 > this.blockSize100k || this.blockSize100k > 9)
			{
				throw new BZip2Exception("Invalid block size");
			}
			this.blockSize100k = newSize100k;
			if (newSize100k != 0)
			{
				int num = 100000 * newSize100k;
				this.byte_0 = new byte[num];
				this.int_0 = new int[num];
			}
		}
		private static void CompressedStreamEOF()
		{
			throw new EndOfStreamException("BZip2 input stream end of compressed stream");
		}
		private static void BlockOverrun()
		{
			throw new BZip2Exception("BZip2 input stream block overrun");
		}
		private static void BadBlockHeader()
		{
			throw new BZip2Exception("BZip2 input stream bad block header");
		}
		private static void CrcError()
		{
			throw new BZip2Exception("BZip2 input stream crc error");
		}
		private static void HbCreateDecodeTables(int[] limit, int[] baseArray, int[] perm, char[] length, int minLen, int maxLen, int alphaSize)
		{
			int num = 0;
			for (int i = minLen; i <= maxLen; i++)
			{
				for (int j = 0; j < alphaSize; j++)
				{
					if ((int)length[j] == i)
					{
						perm[num] = j;
						num++;
					}
				}
			}
			for (int i = 0; i < 23; i++)
			{
				baseArray[i] = 0;
			}
			for (int i = 0; i < alphaSize; i++)
			{
				baseArray[(int)(length[i] + '\u0001')]++;
			}
			for (int i = 1; i < 23; i++)
			{
				baseArray[i] += baseArray[i - 1];
			}
			for (int i = 0; i < 23; i++)
			{
				limit[i] = 0;
			}
			int num2 = 0;
			for (int i = minLen; i <= maxLen; i++)
			{
				num2 += baseArray[i + 1] - baseArray[i];
				limit[i] = num2 - 1;
				num2 <<= 1;
			}
			for (int i = minLen + 1; i <= maxLen; i++)
			{
				baseArray[i] = (limit[i - 1] + 1 << 1) - baseArray[i];
			}
		}
	}
}
